define(function(require, exports, module) {

  // var Backbone = require('backbone');
  var $ = require('jquery');
  var _ = require('underscore');
  // var jsonview = require('jsonview');
  
  require('hy5b/frontend/canvas/index')
  require('hy5b/frontend/canvas/line')

  var addReduce = require('hy5b/animation/add-reduce')

  var getLoadObjMtl = require('./loadObjMtl')

  require('http://localhost/d/threejs-0.106.2/examples/js/shaders/LuminosityHighPassShader')
  require('http://localhost/d/threejs-0.106.2/examples/js/shaders/CopyShader')
  require('http://localhost/d/threejs-0.106.2/examples/js/postprocessing/ShaderPass');
  require('http://localhost/d/threejs-0.106.2/examples/js/postprocessing/EffectComposer');
  require('http://localhost/d/threejs-0.106.2/examples/js/postprocessing/RenderPass');
  require('http://localhost/d/threejs-0.106.2/examples/js/postprocessing/UnrealBloomPass');

  require('http://localhost/d/threejs-0.106.2/examples/js/lines/Line2')
  require('http://localhost/d/threejs-0.106.2/examples/js/lines/LineGeometry')
  require('http://localhost/d/threejs-0.106.2/examples/js/lines/LineMaterial')

  var vertexShaderText = require('text!../shader/vertex.glsl')
  var fragmentshaderText = require('text!../shader/fragment.glsl')

  const loadObjMtl = getLoadObjMtl()
  
  const {
    EffectComposer,
    RenderPass,
    ShaderPass,
    UnrealBloomPass,
    Line2,
    LineGeometry,
    LineMaterial
  } = THREE;

  const BLOOM_SCENE = 1;

  function main($app) {
    //给实际环境写栗子不考虑画布缩放
    const w = 960;
    const h = 540;

    let renderer = initRenderer(w, h)
    $('.webgl').append(renderer.domElement)

    const aspect = w / h;

    const boxSize = 1
    const floorSize = boxSize * 7.2; //7.2是
    const frustumSize = floorSize * 1.3;
    const cameraPosition = boxSize * 4;
    const cameraFar = boxSize * 120;
    const cameraScale = h / floorSize;
    const objectScale = 0.005;

    // const rotationZ = 5 * Math.PI / 6;

    let scene = initScene()
    
    let axesHelper = new THREE.AxesHelper(500);
    scene.add(axesHelper);

    let floor = new THREE.PlaneGeometry(floorSize * aspect, floorSize)
    let floorMaterial = new THREE.MeshBasicMaterial({ color: 0x084073 })
    let floorMesh = new THREE.Mesh(floor, floorMaterial);
    scene.add(floorMesh)

    if (0) {
      let geometry = new THREE.BoxGeometry(boxSize, boxSize, boxSize);
      let material = new THREE.MeshNormalMaterial();
      
      const boxs = [
        // [0,0],
        // [0,h],
        [w,0],
        [w,h],
        [200,465],
        // [442.5,307.5]
      ]
      _.each(boxs, function(offsetPosition) {
        let mesh = new THREE.Mesh(geometry, material);
        mesh.position.x = (-offsetPosition[0] + w / 2) / cameraScale - boxSize / 2
        mesh.position.y = (offsetPosition[1] - h / 2) / cameraScale + boxSize / 2
        scene.add(mesh);
        // mesh.layers.enable(BLOOM_SCENE);
      })
    }
   
    let params = {
      exposure: [1, 0.1],
      bloomStrength: 1,
      bloomThreshold: 0,
      bloomRadius: -0.8,

      dashed: 0,
      dashScale: 1,
      dashSize: 1,
      gapSize: 1,
      linewidth: [0.002, 0.001],
      
      labelDistance: [0.4, 0.1],
      
      lightIntensity: [0.6, 0.1],
      
      cameraRotationZ: 150,
      cameraFrustumSize: frustumSize,
      cameraPositionX: cameraPosition,
      cameraPositionY: cameraPosition,
      cameraPositionZ: cameraPosition,

      type: 1
    };
    
    _.each(params, function(v, param) {
      let val = v;
      let delta;
      if (val.length > 0) {
        delta = val[1]
        val = val[0]
        params[param] = val
      }
      $app.append(
        addReduce({
          set: (function(param) {
            return function(value) {
              params[param] = value;
              step()
            }
          }(param)),
          key: param,
          val: val,
          delta: delta
        })
      )

      $app.append('<br/>')
    })
    
    let light = new THREE.AmbientLight(0xffffff);
    light.position.set(100, 100, 100)
    scene.add(light)
    
    let light1 = new THREE.DirectionalLight(0xffffff, params.lightIntensity);
    light1.position.set(100, 100, 100)
    scene.add(light1)
    
    let camera = initCamera(aspect, 0.01, cameraFar, params.cameraFrustumSize)
    camera.position.x = params.cameraPositionX
    camera.position.y = params.cameraPositionY
    camera.position.z = params.cameraPositionZ
    camera.lookAt(0,0,0)

    camera.rotation.z = params.cameraRotationZ * Math.PI / 180 //要在lookAt后
    
    // let cameraHelper = new THREE.CameraHelper(camera)
    // scene.add(cameraHelper)
    
    // let renderScene = new RenderPass(scene, camera);

    // let bloomPass = new UnrealBloomPass(new THREE.Vector2(w, h), 1.5, 0.4, 0.85);
    // bloomPass.threshold = params.bloomThreshold;
    // bloomPass.strength = params.bloomStrength;
    // bloomPass.radius = params.bloomRadius;

    // let bloomComposer = new EffectComposer(renderer);
    // bloomComposer.renderToScreen = false;
    // bloomComposer.addPass(renderScene);
    // bloomComposer.addPass(bloomPass);

    // let myBloomShaderPass = new ShaderPass(
      // new THREE.ShaderMaterial({
        // uniforms: {
          // baseTexture: { value: null },
          // bloomTexture: { value: bloomComposer.renderTarget2.texture },
          // color1: { value: 0.0 },
          // color2: { value: 0.8 },
          // color3: { value: 0.2 }
        // },
        // vertexShader: vertexShaderText,
        // fragmentShader: fragmentshaderText,
        // defines: {}
      // }), "baseTexture"
    // );
    // myBloomShaderPass.needsSwap = true;

    // let finalComposer = new EffectComposer(renderer);
    // finalComposer.addPass(renderScene);
    // finalComposer.addPass(myBloomShaderPass);

    let lineMaterial;

    let boxs = [
      [0,0],
      [0,75],
      [0,75*2],
      // [0,75*3],
      [0,75*4],
      // [0,75*5],
      [0,75*6],
      [w-75,0],
      [w-75,h-75],
      // [200,h-75],
      [w/2-75/2,h/2],
      [w/2-75/2,h/2-75/2],
      // [w/2-75/2,h/2+75/2]
    ]

    let mold3ds = [
      // 'obj1',
      '1',
      '2',
      '3',
      '4',
      '5',
      '6',
      '7',
      '7',
      '6',
      '7'
    ]
   
    let labelPositions = [
      // 'top',
      // 'bottom',
      // 'left',
      'right'
    ]

    let requestMeshPromises = []
    let mold3d2PromiseMarkMap = {}

    _.each(boxs, function(box, i) {
      const mold3d = mold3ds[i] ? mold3ds[i] : mold3ds[0];
      if (mold3d2PromiseMarkMap[mold3d] === undefined) {
        requestMeshPromises.push(loadObjMtl(mold3d, i))
        mold3d2PromiseMarkMap[mold3d] = i
      }
    })

    Promise.all(requestMeshPromises).then(function(res) {
      let meshs = {}
      _.each(res, function(data) {
        var object3D = data.data;
        let m = new THREE.Matrix4();
        m.makeScale(objectScale, objectScale, objectScale)
        object3D.applyMatrix(m)
        object3D.rotation.x = Math.PI / 2
        object3D.rotation.y = Math.PI
        meshs[data.mark] = object3D;
      })

      _.each(boxs, function(offsetPosition, i) {
        const mold3d = mold3ds[i] ? mold3ds[i] : mold3ds[0];
        const promiseMark = mold3d2PromiseMarkMap[mold3d]
        let objMeshGroup = meshs[promiseMark].clone();
        const pos = transform2dTo3dCoord(offsetPosition[0], offsetPosition[1])
        const centerPoint = [pos.x - boxSize / 2, pos.y + boxSize / 2]
        objMeshGroup.position.x = centerPoint[0]
        objMeshGroup.position.y = centerPoint[1]

        scene.add(objMeshGroup);

        //加载进来的obj是一个group类型的mesh
        const num = Math.random();
        if (num > 0.7) {
          _.each(objMeshGroup.children, function(mesh) {
            mesh.layers.enable(BLOOM_SCENE);
            mesh.material.color = new THREE.Color( 0x8080ff );
          })
        }
        
        // return
        
        _.each(labelPositions, function(position) {
          const setting = { name: 'bloomThreshold', nameFamily: 'Gotham' }
          const labelOption = testLabelCanvasOption(setting, 1.5);
          const labelPlaneWidth = labelOption.width / 75
          const labelPlaneHeight = labelOption.height / 75
          const labelGeometry = new THREE.PlaneGeometry(labelPlaneWidth, labelPlaneHeight);
          const labelMaterial = new THREE.MeshBasicMaterial({
            map: createLabelTexture(setting, labelOption),
            transparent: true
          });
          // labelMaterial.needsUpdate = true
          const labelMesh = new THREE.Mesh(labelGeometry, labelMaterial);
          labelMesh.rotation.x = Math.PI / 2
          labelMesh.rotation.y = Math.PI
          const calcLabelPoint = getCalcLabelPosFun(position, boxSize);
          const labelCenterPoint = calcLabelPoint(centerPoint[0], centerPoint[1], labelPlaneWidth, labelPlaneHeight)
          labelMesh.position.x = labelCenterPoint[0]
          labelMesh.position.y = labelCenterPoint[1]
          labelMesh.position.z = boxSize / 2
          scene.add(labelMesh);
        })
        
      })

      const geometry = new LineGeometry();
      let vertices = []
      _.each([[75/2,75],[75/2,100],[300,100],[300,h-75/2],[w-75,h-75/2]], function(point) {
        const pos = transform2dTo3dCoord(point[0], point[1])
        vertices.push(pos.x, pos.y, 0.05);
      })
      geometry.setPositions(vertices);
      lineMaterial = new LineMaterial({
        color: 0x0080ff,
        dashed: params.dashed > 0,
        dashScale: params.dashScale,
        dashSize: params.dashSize,
        gapSize: params.gapSize,
        linewidth: params.linewidth
      });
      if (params.dashed > 0) {
        lineMaterial.defines.USE_DASH = ""
      } else {
        delete lineMaterial.defines.USE_DASH
      }
      // lineMaterial.needsUpdate = true;
      const line = new Line2(geometry, lineMaterial);
      line.computeLineDistances();
      scene.add(line)

      render()

    })

    // step()

    function step() {
      // requestAnimationFrame(step);

      renderer.toneMappingExposure = Math.pow(params.exposure, 4.0);
      // bloomPass.threshold = Number(params.bloomThreshold);
      // bloomPass.strength = Number(params.bloomStrength);
      // bloomPass.radius = Number(params.bloomRadius);

      lineMaterial.linewidth = params.linewidth
      lineMaterial.dashed = !!params.dashed
      if (params.dashed > 0) {
        lineMaterial.defines.USE_DASH = ""
      } else {
        delete lineMaterial.defines.USE_DASH
      }
      lineMaterial.needsUpdate = true
      lineMaterial.dashScale = params.dashScale
      lineMaterial.dashSize = params.dashSize
      lineMaterial.gapSize = params.gapSize
      
      light1.intensity = params.lightIntensity
      
      initCamera(aspect, 0.01, cameraFar, params.cameraFrustumSize, camera)
      camera.position.x = params.cameraPositionX
      camera.position.y = params.cameraPositionY
      camera.position.z = params.cameraPositionZ
      camera.lookAt(0,0,0)
      camera.rotation.z = params.cameraRotationZ * Math.PI / 180
      
      render()
    }

    function render() {
      switch (params.type) {
        case 1:
          renderer.render(scene, camera);
          break;
        case 2:
          bloomComposer.renderToScreen = true;
          bloomComposer.render()
          break;
        case 3:
        default:
          // render scene with bloom
          scene.traverse(darkenNonBloomed);
          bloomComposer.renderToScreen = false;
          bloomComposer.render();
          scene.traverse(restoreMaterial);
          // render the entire scene, then render bloom scene on top
          finalComposer.render();
          break;
      }
    }

    let materials = []
    let darkMaterial = new THREE.MeshBasicMaterial({ color: "black" });
    let bloomLayer = new THREE.Layers();
    bloomLayer.set(BLOOM_SCENE);

    function darkenNonBloomed(obj) {
      if (obj.isMesh && bloomLayer.test(obj.layers) === false) {
        materials[obj.uuid] = obj.material;
        obj.material = darkMaterial;
      }
    }

    function restoreMaterial(obj) {
      if (materials[obj.uuid]) {
        obj.material = materials[obj.uuid];
        delete materials[obj.uuid];
      }
    }

    function transform2dTo3dCoord(relX, relY) {
      return {
        x: (-relX + w / 2) / cameraScale,
        y: (relY - h / 2) / cameraScale
      }
    }
  }
  
  function initRenderer(w, h, renderer) {
    if (!renderer) {
      let renderer = new THREE.WebGLRenderer({ alpha: true, antialias: true });
      renderer.setSize(w, h);
      // renderer.setClearColor(0x071b33, 0);
      renderer.setPixelRatio(window.devicePixelRatio)

      // renderer.shadowMap.enabled = true;
      // renderer.shadowMapType = THREE.PCFSoftShadowMap;
      return renderer;
    } else {
      renderer.setSize(w, h)
    }
  }

  function initScene() {
    let scene = new THREE.Scene()
    return scene;
  }
  
  function initCamera(aspect, near, far, frustumSize, camera) { //0.68
    if (!camera) {
      let camera = new THREE.OrthographicCamera(
        frustumSize * aspect / -2,
        frustumSize * aspect / 2,
        frustumSize / 2,
        frustumSize / -2, near, far)
      return camera;
    } else {
      camera.left = frustumSize * aspect / -2
      camera.right = frustumSize * aspect / 2
      camera.top = frustumSize / 2
      camera.bottom = frustumSize / -2
      camera.near = near
      camera.far = far
      camera.updateProjectionMatrix()
    }
  }
  
  function getCalcLabelPosFun(position, boxSize) {
    const labelDistance = 0.2;
    switch (position) {
      case 'right':
        return (x, y, w, h) => {
          return [x - w / 2 - boxSize / 2 - labelDistance, y]
        }
      case 'left':
        return (x, y, w, h) => {
          return [x + w / 2 + boxSize / 2 + labelDistance, y]
        }
      case 'top':
        return (x, y, w, h) => {
          return [x, y - h / 2 - boxSize / 2 - labelDistance]
        }
      case 'bottom':
      default:
        return (x, y, w, h) => {
          return [x, y + h / 2 + boxSize / 2 + labelDistance]
        }
    }
  }
  
  function testLabelCanvasOption(setting, textAspect) {
    const offscreenCanvas = document.createElement('canvas');
    const ctx = offscreenCanvas.getContext('2d');
    const fontSize = (+setting.nameSize || 12) * textAspect;
    //example: italic bold 40px serif
    const font = (setting.nameStyle ? setting.nameStyle : '')
      + (setting.nameWeight ? (' ' + setting.nameWeight) : '')
      + (' ' + fontSize + 'px')
      + (setting.nameFamily ? (' ' + setting.nameFamily) : '')
    ctx.font = font;
    const labelWidth = ctx.measureText(setting.name).width;
    const offset = 5;
    return { width: labelWidth + offset * 2, height: fontSize + offset * 2, offset, font };
  }

  function createLabelTexture(setting, labelOption) {
    const width = labelOption.width
    const height = labelOption.height
    
    const $elem = $('<canvas>')
      .attr('width', width)
      .attr('height', height)
      
    const canvas = $elem[0];
    const ctx = canvas.getContext('2d');
    var grd = ctx.createLinearGradient(width * 0.3, 0, width * 0.7, height);
    grd.addColorStop(0, "#162242");
    grd.addColorStop(1, "#205282");
    ctx.fillStyle = grd;
    ctx.fillRect(0,0,width,height);
    
    $elem
      .drawText({
        fillStyle: '#fff',
        x: labelOption.offset,
        y: labelOption.offset,
        textBaseline: 'hanging',
        font: labelOption.font,
        text: setting.name
      })
      // .drawLine({
        // points: [[0,0],[width,0],[width, height],[0,height]],
        // lineWidth: 5,
        // strokeStyle: '#fff'
      // })
    
    const mapping = THREE.UVMapping;
    const wrapS = THREE.ClampToEdgeWrapping;
    const wrapT = THREE.ClampToEdgeWrapping;
    const magFilter = THREE.LinearFilter;
    const minFilter = THREE.LinearMipMapLinearFilter;
    const format = THREE.RGBAFormat;
    const type = THREE.UnsignedByteType;
    const anisotropy = 1;
    return new THREE.CanvasTexture(canvas, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy)
  }

  return main;
})
